package chap_06;

import chap_04.Dish;
import org.junit.jupiter.api.Test;

import java.util.*;
import java.util.stream.Collectors;

/**
 * 分组
 */
public class Run_02 {
    public enum CaloricLevel {DIET, NORMAL, FAT}

    ;

    List<Dish> menu = Arrays.asList(
            new Dish("pork", false, 800, Dish.Type.MEAT),
            new Dish("beef", false, 700, Dish.Type.MEAT),
            new Dish("chicken", false, 400, Dish.Type.MEAT),
            new Dish("french fries", true, 530, Dish.Type.OTHER),
            new Dish("rice", true, 350, Dish.Type.OTHER),
            new Dish("season fruit", true, 120, Dish.Type.OTHER),
            new Dish("pizza", true, 550, Dish.Type.OTHER),
            new Dish("prawns", false, 300, Dish.Type.FISH),
            new Dish("salmon", false, 450, Dish.Type.FISH));


    @Test
    public void demo_01() {
        Map<Dish.Type, List<Dish>> dishesByType = menu.stream().collect(Collectors.groupingBy(Dish::getType));
        System.out.println(dishesByType);
    }

    /**
     * 把热量不到400卡路里的菜划分为“低热量”（diet），热量400到700
     * 卡路里的菜划为“普通”（normal），高于700卡路里的划为“高热量”（fat）
     */
    @Test
    public void demo_02() {
        Map<String, List<Dish>> collect = menu.stream()
                .collect(Collectors.groupingBy((Dish dish) -> {
                    if (dish.getCalories() <= 400) {
                        return "DIET";
                    } else if (dish.getCalories() <= 700) {
                        return "NORMAL";
                    } else {
                        return "FAT";
                    }
                }));

        System.out.println(collect);
    }

    /**
     * 多级分组
     */
    @Test
    public void demo_03() {
        Map<Dish.Type, Map<String, List<Dish>>> collect = menu.stream()
                .collect(Collectors.groupingBy(Dish::getType,
                        Collectors.groupingBy(
                                (Dish dish) -> {
                                    if (dish.getCalories() <= 400) {
                                        return "DIET";
                                    } else if (dish.getCalories() <= 700) {
                                        return "NORMAL";
                                    } else {
                                        return "FAT";
                                    }
                                }
                        )));

        System.out.println(collect);
    }

    /**
     * 按子组收集数据
     * 传递给第一个groupingBy的第二个收集器可以是任何类型，而不一定是另一个groupingBy
     */
    @Test
    public void demo_04() {
        Map<Dish.Type, Long> collect = menu.stream().collect(Collectors.groupingBy(Dish::getType, Collectors.counting()));
        Map<Dish.Type, IntSummaryStatistics> summaryStatisticsMap = menu.stream().collect(Collectors.groupingBy(Dish::getType, Collectors.summarizingInt(Dish::getCalories)));
        Map<Dish.Type, Integer> totalCalories = menu.stream().collect(Collectors.groupingBy(Dish::getType, Collectors.summingInt((a) -> a.getCalories())));
        System.out.println(collect);
        System.out.println(summaryStatisticsMap);
        System.out.println("totalCalories: " + totalCalories);
    }

    /**
     * 按照分类取热量最高的
     */
    @Test
    public void demo_05() {
        Map<Dish.Type, Optional<Dish>> collect = menu.stream().collect(Collectors.groupingBy(Dish::getType,
                Collectors.maxBy(Comparator.comparingInt(Dish::getCalories))));

        System.out.println(collect);
    }

    /**
     * 把收集器的结果转换为另一种类型
     */
    @Test
    public void demo_06() {
        Map<Dish.Type, Dish> mostCaloricByType =
                menu.stream()
                        .collect(Collectors.groupingBy(Dish::getType,
                                Collectors.collectingAndThen(
                                        Collectors.maxBy(Comparator.comparingInt(Dish::getCalories)),
                                        Optional::get)));

        System.out.println(mostCaloricByType);
    }

    /**
     * 与groupingBy联合使用的其他收集器的例子
     */
    @Test
    public void demo_07() {
        Map<Dish.Type, Integer> collect = menu.stream().
                collect(Collectors.groupingBy(Dish::getType, Collectors.summingInt(Dish::getCalories)));

        System.out.println(collect);
    }

    /**
     * 常常和groupingBy联合使用的另一个收集器是mapping方法生成的。这个方法接受两
     * 个参数：一个函数对流中的元素做变换，另一个则将变换的结果对象收集起来。其目的是在累加
     * 之前对每个输入元素应用一个映射函数，这样就可以让接受特定类型元素的收集器适应不同类型
     * 的对象。
     */
    @Test
    public void demo_08() {
        Map<Dish.Type, List<String>> collect = menu.stream().collect(Collectors.groupingBy(Dish::getType,
                Collectors.mapping((Dish dish) -> {
                    if (dish.getCalories() <= 400) {
                        return "DIET";
                    } else if (dish.getCalories() <= 700) {
                        return "NORMAL";
                    } else {
                        return "FAT";
                    }
                }, Collectors.toList())
        ));

        System.out.println(collect);
    }

    @Test
    public void demo_09() {
        Map<Dish.Type, HashSet<String>> collect = menu.stream().collect(Collectors.groupingBy(Dish::getType,
                Collectors.mapping((Dish dish) -> {
                    if (dish.getCalories() <= 400) {
                        return "DIET";
                    } else if (dish.getCalories() <= 700) {
                        return "NORMAL";
                    } else {
                        return "FAT";
                    }
                }, Collectors.toCollection(HashSet::new))
        ));

        System.out.println(collect);
    }
}
